package com.module.pay.wx

import android.content.Context
import com.google.common.io.ByteStreams
import com.google.gson.Gson
import com.module.pay.Base64
import com.module.pay.wx.bean.AmountBean
import com.module.pay.wx.bean.WxPlay
import com.module.pay.wx.http.getPayService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl
import java.io.IOException
import java.security.KeyFactory
import java.security.NoSuchAlgorithmException
import java.security.PrivateKey
import java.security.Signature
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec

class WXPayUtils {
    companion object {
        var schema = "WECHATPAY2-SHA256-RSA2048 "
        var url: String = "https://api.mch.weixin.qq.com/v3/pay/transactions/app"
        lateinit var privateKey: String
        fun addOrder(
            context: Context,
            wxAppId: String,
            wxMchId: String,
            wxSerialNo: String,
            desc: String,
            total: Int,
            listener: WxPayListener
        ) {
            try {
                WXPayUtils().addOrder2(context, wxAppId, wxMchId, wxSerialNo, desc, total, listener)
            } catch (e: Exception) {
                e.printStackTrace()
                listener?.onFail("微信支付异常")
            }
        }
    }


    /**
     * 下单
     */
    @Throws(java.lang.Exception::class)
    fun addOrder2(
        context: Context,
        wxAppId: String,
        wxMchId: String,
        wxSerialNo: String,
        desc: String,
        total: Int,
        listener: WxPayListener?
    ) {
        GlobalScope.launch(Dispatchers.Main) {
            kotlin.runCatching {
                //加密，这里只列举必填字段
                // 1. 拼接下单地址参数
                val nonceStr = CommonUtil.getRandomString(32)
                val notify_url = "https://www.baidu.com/"
                val out_trade_no = System.currentTimeMillis().toString() + ""
//                val ip = "192.168.0.67"
//                val total_fee = total.toString() + ""
//                val trade_type = "APP"
                /**v2版本*/
//                  TreeMap<String, String> param = new TreeMap<String, String>();
//                  param.put("appid", wxAppId); // 公总号ID
//                  param.put("mch_id", wxMchId); // 商户号ID
//                  param.put("device_info", "10000"); // 商户号ID
//                  param.put("body", body);// 商品描述
//                  param.put("nonce_str", nonceStr);  // 随机字符串
//                  param.put("out_trade_no", out_trade_no); // 商户订单号
//                  param.put("total_fee", total_fee);//金额（分）
//                  param.put("spbill_create_ip", ip); // 商户终端ip
//                  param.put("notify_url", notify_url); // H5微信异步通知回调地址
//                  param.put("trade_type", trade_type); // H5支付类型
//                  // 生成签名,官方默认MD5+商户秘钥+参数信息
//                  String sign = CommonUtil.createSign("yxe0310circlq0607yyz1020yj0601WX", "UTF-8", param);
//                  param.put("sign", sign);
//                  String xml = CommonUtil.getRequestXml(param);
                /**结束 */

                /**v3版本 */
                val wxPlay = WxPlay()
                wxPlay.mchid = wxMchId
                wxPlay.appid = wxAppId
                wxPlay.description = desc
                wxPlay.notifyUrl = notify_url
                wxPlay.outTradeNo = out_trade_no
                val amountBean = AmountBean()
                amountBean.total = total
                amountBean.currency = "CNY"
                wxPlay.amount = amountBean
                var authorization = withContext(Dispatchers.IO) {
                    //读取证书
                    val inputStream = context.assets.open("wx/apiclient_key.pem")
                    privateKey = String(ByteStreams.toByteArray(inputStream))
                    //加密
                    val token: String = getToken(
                        "POST",
                        HttpUrl.parse(url),
                        wxMchId,
                        wxSerialNo,
                        nonceStr,
                        privateKey,
                        Gson().toJson(wxPlay)
                    )
                    val authorization = schema + token
                    return@withContext authorization
                }
                //执行接口请求
                var result = withContext(Dispatchers.IO) {
                    getPayService().addOrder(authorization, wxPlay);
                }
                //组装数据
                result.appId = wxAppId
                result.partnerId = wxMchId
                result.nonceStr = nonceStr
                val timestamp = System.currentTimeMillis() / 1000
                result.timestamp = timestamp.toString() + ""
                val message =
                    buildMessage2(result.appId, timestamp, nonceStr, result.prepayId)
                val signature = sign(message?.toByteArray(charset("UTF-8")), privateKey)
                result.sign = signature
                return@runCatching result
            }.onSuccess {
                listener?.onSuccess(it)
            }.onFailure {
                listener?.onFail("微信支付异常")
            }
        }
    }


    /**
     * 生成组装请求头
     *
     * @param method     请求方式
     * @param url        请求地址
     * @param mercId     商户ID
     * @param serial_no  证书序列号
     * @param privateKey 私钥值
     * @param body       请求体
     * @return 组装请求的数据
     * @throws Exception
     */
    @Throws(java.lang.Exception::class)
    fun getToken(
        method: String,
        url: HttpUrl?,
        mercId: String,
        serial_no: String,
        nonceStr: String,
        privateKey: String,
        body: String
    ): String {
        val timestamp = System.currentTimeMillis() / 1000
        val message = buildMessage(method, url, timestamp, nonceStr, body)
        val signature = sign(message.toByteArray(charset("UTF-8")), privateKey)
        return ("mchid=\"" + mercId + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + serial_no + "\","
                + "signature=\"" + signature + "\"")
    }

    /**
     * 生成签名
     *
     * @param message    请求体
     * @param privateKey 私钥值
     * @return 生成base64位签名信息
     * @throws Exception
     */
    @Throws(java.lang.Exception::class)
    fun sign(message: ByteArray?, privateKey: String): String {
        val sign = Signature.getInstance("SHA256withRSA")
        sign.initSign(getPrivateKey(privateKey))
        sign.update(message)
        return Base64.encode(sign.sign())
    }

    fun buildMessage(
        method: String,
        url: HttpUrl?,
        timestamp: Long,
        nonceStr: String,
        body: String
    ): String {
        var canonicalUrl = url?.encodedPath()
        if (url?.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery()
        }
        return """
             $method
             $canonicalUrl
             $timestamp
             $nonceStr
             $body
             
             """.trimIndent()
    }

    /**
     * app用的签名
     */
    fun buildMessage2(appid: String, timestamp: Long, nonceStr: String, prepayId: String): String? {
        return """
             $appid
             $timestamp
             $nonceStr
             $prepayId
             
             """.trimIndent()
    }


    /**
     * 获取私钥。
     *
     * @param privateKey 私钥值
     * @return 私钥对象
     */
    @Throws(IOException::class)
    fun getPrivateKey(privateKey: String): PrivateKey? {
        var privateKey = privateKey
        return try {
            privateKey = privateKey.replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replace("\\s+".toRegex(), "")
            val kf = KeyFactory.getInstance("RSA")
            kf.generatePrivate(
                PKCS8EncodedKeySpec(Base64.decode(privateKey))
            )
        } catch (e: NoSuchAlgorithmException) {
            throw RuntimeException("当前Java环境不支持RSA", e)
        } catch (e: InvalidKeySpecException) {
            throw RuntimeException("无效的密钥格式")
        }
    }
}